/**
*****************************************************************************************
*     Copyright(c) 2015, Realtek Semiconductor Corporation. All rights reserved.
*****************************************************************************************
* @file     firmware_update_server.c
* @brief    Source file for firmware update server model.
* @details  Data types and external functions declaration.
* @author   hector_huang
* @date     2020-10-15
* @version  v1.0
* *************************************************************************************
*/

#define MM_ID MM_MODEL

/* Add Includes here */
#include <string.h>
#include "mesh_api.h"
#include "firmware_update.h"
#include "blob_transfer.h"
#include "generic_types.h"

#if MESH_DFU

mesh_model_info_t fw_update_server;
fw_update_server_ctx_t fw_update_server_ctx;

typedef struct _fw_info_e_t
{
    struct _fw_info_e_t *pnext;
    fw_info_t fw_info;
    uint8_t cached_metadata_len;
    uint8_t cached_metadata[255];
} fw_info_e_t;

bool fw_update_server_add_info(fw_id_t *pfw_id, uint8_t fw_id_len,
                               uint8_t *pupdate_uri, uint8_t update_uri_len)
{
    fw_info_e_t *pentry = plt_zalloc(sizeof(fw_info_e_t), RAM_TYPE_DATA_ON);
    if (NULL == pentry)
    {
        printe("fw_info_add: add info failed, out of memory");
        return false;
    }

    memcpy(&pentry->fw_info.fw_id, (uint8_t *)pfw_id, fw_id_len);
    pentry->fw_info.fw_id_len = fw_id_len;
    memcpy(pentry->fw_info.update_uri, pupdate_uri, update_uri_len);
    pentry->fw_info.update_uri_len = update_uri_len;

    plt_list_push(&fw_update_server_ctx.fw_info_list, pentry);

    return true;
}

static fw_info_e_t *fw_info_get(uint8_t idx)
{
    if (idx > fw_update_server_ctx.fw_info_list.count)
    {
        return NULL;
    }

    fw_info_e_t *pentry = (fw_info_e_t *)fw_update_server_ctx.fw_info_list.pfirst;
    while (idx --)
    {
        pentry = pentry->pnext;
    }

    return pentry;
}

static void fw_update_server_clear_cached_metadata(uint8_t fw_image_idx)
{
    fw_info_e_t *pfw_info = fw_info_get(fw_image_idx);
    if (NULL != pfw_info)
    {
        pfw_info->cached_metadata_len = 0;
    }
}

void fw_update_server_set_verify_result(bool success)
{
    if (success)
    {
        fw_update_server_ctx.update_phase = FW_UPDATE_PHASE_VERIFICATION_SUCCEEDED;
    }
    else
    {
        fw_update_server_ctx.update_phase = FW_UPDATE_PHASE_VERIFICATION_FAILED;
        /* clear cached metadata */
        fw_update_server_clear_cached_metadata(fw_update_server_ctx.update_fw_image_idx);
    }
}

void fw_update_server_clear(void)
{
    printi("fw_update_server_clear");
    if ((NULL != fw_update_server.model_data_cb) &&
        (FW_UPDATE_PHASE_VERIFYING_UPDATE == fw_update_server_ctx.update_phase))
    {
        fw_update_server_verify_cancel_t cancel_data;
        cancel_data.fw_image_idx = fw_update_server_ctx.update_fw_image_idx;
        fw_update_server.model_data_cb(&fw_update_server, FW_UPDATE_SERVER_VERIFY_CANCEL, &cancel_data);
    }

    if (fw_update_server_ctx.update_phase != FW_UPDATE_PHASE_IDLE)
    {
        fw_update_server_ctx.update_phase = FW_UPDATE_PHASE_IDLE;
        fw_update_server_clear_cached_metadata(fw_update_server_ctx.update_fw_image_idx);
    }

    /* clear blob transfer server data */
    blob_transfer_server_clear();
}

int32_t fw_update_handle_blob_server_data(const mesh_model_info_p pmodel_info, uint32_t type,
                                          void *pargs)
{
    switch (type)
    {
    case BLOB_TRANSFER_SERVER_BLOCK_DATA:
        if (NULL != fw_update_server.model_data_cb)
        {
            blob_transfer_server_block_data_t *pdata = (blob_transfer_server_block_data_t *)pargs;
            fw_update_server_block_data_t block_data;
            block_data.block_num = pdata->block_num;
            block_data.pdata = pdata->pdata;
            block_data.data_len = pdata->data_len;
            fw_update_server.model_data_cb(&fw_update_server, FW_UPDATE_SERVER_BLOCK_DATA, &block_data);
        }
        break;
    case BLOB_TRANSFER_SERVER_COMPLETE:
        fw_update_server_ctx.update_phase = FW_UPDATE_PHASE_VERIFYING_UPDATE;
        if (NULL != fw_update_server.model_data_cb)
        {
            //blob_transfer_server_complete_t *pdata = (blob_transfer_server_complete_t *)pargs;
            fw_update_server_verify_t verify_data;
            verify_data.fw_image_idx = fw_update_server_ctx.update_fw_image_idx;
            fw_update_server.model_data_cb(&fw_update_server, FW_UPDATE_SERVER_VERIFY, &verify_data);
        }
        break;
    case BLOB_TRANSFER_SERVER_CANCEL:
        fw_update_server_ctx.update_phase = FW_UPDATE_PHASE_TRANSFER_ERR;
        /* clear blob transfer state */
        fw_update_server_clear_cached_metadata(fw_update_server_ctx.update_fw_image_idx);
        blob_transfer_server_clear();
        if (NULL != fw_update_server.model_data_cb)
        {
            //blob_transfer_server_cancel_t *pdata = (blob_transfer_server_cancel_t *)pargs;
            fw_update_server_fail_t fail_data;
            fail_data.fw_image_index = fw_update_server_ctx.update_fw_image_idx;
            fail_data.reason = FW_UPDATE_FAIL_REASON_USER_CANCEL;
            fw_update_server.model_data_cb(&fw_update_server, FW_UPDATE_SERVER_FAIL, &fail_data);
        }
        break;
    case BLOB_TRANSFER_SERVER_SUSPEND:
        fw_update_server_ctx.update_phase = FW_UPDATE_PHASE_TRANSFER_ERR;
        /* clear blob transfer state */
        fw_update_server_clear_cached_metadata(fw_update_server_ctx.update_fw_image_idx);
        blob_transfer_server_clear();
        if (NULL != fw_update_server.model_data_cb)
        {
            //blob_transfer_server_fail_t *pdata = (blob_transfer_server_fail_t *)pargs;
            fw_update_server_fail_t fail_data;
            fail_data.fw_image_index = fw_update_server_ctx.update_fw_image_idx;
            fail_data.reason = FW_UPDATE_FAIL_REASON_TRANSFER_TIMEOUT;
            fw_update_server.model_data_cb(&fw_update_server, FW_UPDATE_SERVER_FAIL, &fail_data);
        }
        break;
    case BLOB_TRANSFER_SERVER_FAIL:
        fw_update_server_ctx.update_phase = FW_UPDATE_PHASE_TRANSFER_ERR;
        /* clear blob transfer state */
        fw_update_server_clear_cached_metadata(fw_update_server_ctx.update_fw_image_idx);
        blob_transfer_server_clear();
        if (NULL != fw_update_server.model_data_cb)
        {
            //blob_transfer_server_fail_t *pdata = (blob_transfer_server_fail_t *)pargs;
            fw_update_server_fail_t fail_data;
            fail_data.fw_image_index = fw_update_server_ctx.update_fw_image_idx;
            fail_data.reason = FW_UPDATE_FAIL_REASON_TRANSFER_ERR;
            fw_update_server.model_data_cb(&fw_update_server, FW_UPDATE_SERVER_FAIL, &fail_data);
        }
    default:
        break;
    }
    return MODEL_SUCCESS;
}

static mesh_msg_send_cause_t fw_update_server_send(uint16_t dst, uint16_t app_key_index,
                                                   uint8_t *pmsg, uint16_t len)
{
    mesh_msg_t mesh_msg;
    mesh_msg.pmodel_info = &fw_update_server;
    access_cfg(&mesh_msg);
    mesh_msg.pbuffer = pmsg;
    mesh_msg.msg_len = len;
    if (0 != dst)
    {
        mesh_msg.dst = dst;
        mesh_msg.app_key_index = app_key_index;
    }
    return access_send(&mesh_msg);
}

mesh_msg_send_cause_t fw_info_status(uint16_t dst, uint16_t app_key_index, uint8_t first_idx,
                                     uint8_t entries_limit)
{
    mesh_msg_send_cause_t ret;
    uint16_t msg_len = sizeof(fw_update_info_status_t);
    uint8_t idx = 0;
    fw_info_e_t *pentry = (fw_info_e_t *)fw_update_server_ctx.fw_info_list.pfirst;
    while (NULL != pentry)
    {
        if ((idx >= first_idx) && ((idx - first_idx) < entries_limit))
        {
            msg_len += (pentry->fw_info.fw_id_len + pentry->fw_info.update_uri_len + 2);
        }
        pentry = pentry->pnext;
        idx ++;
    }

    fw_update_info_status_t *pmsg = (fw_update_info_status_t *)plt_malloc(msg_len, RAM_TYPE_DATA_ON);
    if (pmsg == NULL)
    {
        printe("fw_info_status: failed, out of memory");
        return MESH_MSG_SEND_CAUSE_NO_MEMORY;
    }

    ACCESS_OPCODE_BYTE(pmsg->opcode, MESH_MSG_FW_UPDATE_INFO_STATUS);
    pmsg->fw_info_list_cnt = fw_update_server_ctx.fw_info_list.count;
    pmsg->first_index = first_idx;
    idx = 0;
    uint8_t *pdata = pmsg->fw_info_list;
    pentry = (fw_info_e_t *)fw_update_server_ctx.fw_info_list.pfirst;
    while (NULL != pentry)
    {
        if ((idx >= first_idx) && ((idx - first_idx) < entries_limit))
        {
            *pdata ++ = pentry->fw_info.fw_id_len;
            memcpy(pdata, &pentry->fw_info.fw_id, pentry->fw_info.fw_id_len);
            pdata += pentry->fw_info.fw_id_len;
            *pdata ++ = pentry->fw_info.update_uri_len;
            memcpy(pdata, pentry->fw_info.update_uri, pentry->fw_info.update_uri_len);
        }
        pentry = pentry->pnext;
        idx ++;
    }

    ret = fw_update_server_send(dst, app_key_index, (uint8_t *)pmsg, msg_len);
    plt_free(pmsg, RAM_TYPE_DATA_ON);

    return ret;
}

mesh_msg_send_cause_t fw_metadata_status(uint16_t dst, uint16_t app_key_index, uint8_t fw_image_idx,
                                         uint8_t *pfw_metadata, uint8_t metadata_len)
{
    fw_update_fw_metadata_status_t msg;
    ACCESS_OPCODE_BYTE(msg.opcode, MESH_MSG_FW_UPDATE_FW_METADATA_STATUS);
    msg.status = FW_UPDATE_STATUS_SUCCESS;
    msg.addi_info = FW_UPDATE_ADDI_INFO_COMP_NO_CHANGE;
    if (fw_image_idx >= fw_update_server_ctx.fw_info_list.count)
    {
        printe("fw_metadata_status: invalid firmware index %d-%d", fw_image_idx,
               fw_update_server_ctx.fw_info_list.count);
        msg.status = FW_UPDATE_STATUS_WRONG_FW_INDEX;
    }
    else
    {
        if (NULL != fw_update_server.model_data_cb)
        {
            fw_update_status_code_t status = FW_UPDATE_STATUS_SUCCESS;
            fw_update_addi_info_t addi_info = FW_UPDATE_ADDI_INFO_COMP_NO_CHANGE;
            fw_update_server_metadata_check_t metadata_check;
            metadata_check.fw_image_idx = fw_image_idx;
            metadata_check.pmetadata = pfw_metadata;
            metadata_check.metadata_len = metadata_len;
            metadata_check.pstatus = (fw_update_status_code_t *)&status;
            metadata_check.paddi_info = (fw_update_addi_info_t *)&addi_info;
            fw_update_server.model_data_cb(&fw_update_server, FW_UPDATE_SERVER_METADATA_CHECK, &metadata_check);
            msg.status = status;
            msg.addi_info = addi_info;
        }
    }
    msg.fw_image_idx = fw_image_idx;
    return fw_update_server_send(dst, app_key_index, (uint8_t *)&msg, sizeof(msg));
}

mesh_msg_send_cause_t fw_update_status(uint16_t dst, uint16_t app_key_index,
                                       fw_update_status_code_t status, uint8_t update_ttl, uint16_t update_timeout_base,
                                       uint8_t blob_id[8], uint8_t update_fw_image_idx)
{
    fw_update_status_t msg;
    uint16_t msg_len = MEMBER_OFFSET(fw_update_status_t, update_ttl);
    ACCESS_OPCODE_BYTE(msg.opcode, MESH_MSG_FW_UPDATE_STATUS);
    msg.status = status;
    msg.rfu1 = 0;
    msg.update_phase = fw_update_server_ctx.update_phase;
    if (fw_update_server_ctx.update_phase != FW_UPDATE_PHASE_IDLE)
    {
        msg.update_ttl = update_ttl;
        msg.addi_info = fw_update_server_ctx.addi_info;
        msg.rfu2 = 0;
        msg.update_timeout_base = update_timeout_base;
        memcpy(msg.blob_id, blob_id, 8);
        msg.update_fw_image_idx = update_fw_image_idx;
        msg_len = sizeof(msg);
    }
    return fw_update_server_send(dst, app_key_index, (uint8_t *)&msg, msg_len);
}

static void fw_update_server_handle_update_start(mesh_msg_p pmesh_msg)
{
    fw_update_status_code_t status = FW_UPDATE_STATUS_SUCCESS;
    uint8_t *pbuffer = pmesh_msg->pbuffer + pmesh_msg->msg_offset;
    fw_update_start_t *pmsg = (fw_update_start_t *)pbuffer;
    uint8_t metadata_len = pmesh_msg->msg_len - sizeof(fw_update_start_t);
    uint8_t *pfw_metadata = pmsg->fw_metadata;
    if (metadata_len == 0)
    {
        pfw_metadata = NULL;
    }

    switch (fw_update_server_ctx.update_phase)
    {
    case FW_UPDATE_PHASE_IDLE:
    case FW_UPDATE_PHASE_TRANSFER_ERR:
    case FW_UPDATE_PHASE_VERIFICATION_FAILED:
        {
            if (blob_transfer_server_busy())
            {
                status = FW_UPDATE_STATUS_BLOB_TRANSFER_BUSY;
                printe("fw_update_server_handle_update_start: blob transfer busy, phase %d",
                       blob_transfer_server_phase_get());
                break;
            }

            fw_info_e_t *pfw_info = fw_info_get(pmsg->fw_image_idx);
            if (NULL == pfw_info)
            {
                printe("fw_update_server_handle_update_start: invalid fw image index %d-%d", pmsg->fw_image_idx,
                       fw_update_server_ctx.fw_info_list.count);
                break;
            }

            if (NULL != fw_update_server.model_data_cb)
            {
                /* check metadata */
                fw_update_status_code_t check_status = FW_UPDATE_STATUS_SUCCESS;
                fw_update_addi_info_t addi_info = FW_UPDATE_ADDI_INFO_COMP_NO_CHANGE;
                fw_update_server_metadata_check_t metadata_check;
                metadata_check.pmetadata = pmsg->fw_metadata;
                metadata_check.metadata_len = metadata_len;
                metadata_check.pstatus = (fw_update_status_code_t *)&check_status;
                metadata_check.paddi_info = (fw_update_addi_info_t *)&addi_info;
                fw_update_server.model_data_cb(&fw_update_server, FW_UPDATE_SERVER_METADATA_CHECK, &metadata_check);
                if (check_status != FW_UPDATE_STATUS_SUCCESS)
                {
                    printe("fw_update_server_handle_update_start: check metadata failed");
                    status = FW_UPDATE_STATUS_METADATA_CHECK_FAILED;
                    break;
                }

                /* check start */
                fw_update_server_start_t update_start;
                bool can_update = true;
                update_start.fw_image_idx = pmsg->fw_image_idx;
                update_start.pcan_update = &can_update;
                fw_update_server.model_data_cb(&fw_update_server, FW_UPDATE_SERVER_START, &update_start);
                if (!can_update)
                {
                    printe("fw_update_server_handle_update_start: can't update now");
                    status = FW_UPDATE_STATUS_TEMP_UNAVAIL;
                    break;
                }
            }

            fw_update_server_ctx.update_ttl = pmsg->update_ttl;
            fw_update_server_ctx.update_timeout_base = pmsg->update_timeout_base;
            fw_update_server_ctx.update_fw_image_idx = pmsg->fw_image_idx;
            memcpy(fw_update_server_ctx.blob_id, pmsg->blob_id, 8);

            if ((pfw_metadata == NULL) || ((metadata_len != pfw_info->cached_metadata_len) ||
                                           (0 != memcmp(pmsg->fw_metadata, pfw_info->cached_metadata, metadata_len))))
            {
                /* new image */
                fw_update_server_ctx.update_phase = FW_UPDATE_PHASE_TRANSFER_ACTIVE;
                pfw_info->cached_metadata_len = metadata_len;
                memcpy(pfw_info->cached_metadata, pmsg->fw_metadata, metadata_len);
                /* prepare blob transfer */
                blob_transfer_server_init(pmsg->blob_id, pmsg->update_timeout_base, pmsg->update_ttl);
            }
            else
            {
                /* verify image */
                fw_update_server_ctx.update_phase = FW_UPDATE_PHASE_VERIFYING_UPDATE;
                fw_update_server_verify_t verify_data;
                verify_data.fw_image_idx = pmsg->fw_image_idx;
                if (NULL != fw_update_server.model_data_cb)
                {
                    fw_update_server.model_data_cb(&fw_update_server, FW_UPDATE_SERVER_VERIFY, &verify_data);
                }
            }

            printi("fw_update_server_handle_update_start: update_ttl %d, update_timeout_base %d, fw_image_idx %d",
                   pmsg->update_ttl, pmsg->update_timeout_base, pmsg->fw_image_idx);
            printi("fw_update_server_handle_update_start: blob id = ");
            dprinti(pmsg->blob_id, 8);
            printi("fw_update_server_handle_update_start: fw_metadata = ");
            dprinti(pmsg->fw_metadata, metadata_len);
        }
        break;
    case FW_UPDATE_PHASE_TRANSFER_ACTIVE:
    case FW_UPDATE_PHASE_VERIFYING_UPDATE:
    case FW_UPDATE_PHASE_VERIFICATION_SUCCEEDED:
    case FW_UPDATE_PHASE_APPLYING_UPDATE:
        if ((fw_update_server_ctx.update_ttl != pmsg->update_ttl) ||
            (fw_update_server_ctx.update_timeout_base != pmsg->update_timeout_base) ||
            (fw_update_server_ctx.update_fw_image_idx != pmsg->fw_image_idx) ||
            (0 != memcmp(fw_update_server_ctx.blob_id, pmsg->blob_id, 8)))
        {
            printe("fw_update_server_handle_update_start: wrong phase, value mismatched");
            status = FW_UPDATE_STATUS_WRONG_PHASE;
            break;
        }
        else
        {
            fw_info_e_t *pfw_info = fw_info_get(pmsg->fw_image_idx);
            if ((metadata_len != pfw_info->cached_metadata_len) ||
                (0 != memcmp(pmsg->fw_metadata, pfw_info->cached_metadata, metadata_len)))
            {
                printe("fw_update_server_handle_update_start: wrong phase, value mismatched");
                status = FW_UPDATE_STATUS_WRONG_PHASE;
                break;
            }
        }

        printi("fw_update_server_handle_update_start: already started");
        break;
    default:
        break;
    }

    fw_update_status(pmesh_msg->src, pmesh_msg->app_key_index, status, pmsg->update_ttl,
                     pmsg->update_timeout_base, pmsg->blob_id, pmsg->fw_image_idx);
}

void fw_update_server_handle_update_apply(uint16_t dst, uint16_t app_key_index)
{
    fw_update_status_code_t status = FW_UPDATE_STATUS_SUCCESS;
    switch (fw_update_server_ctx.update_phase)
    {
    case FW_UPDATE_PHASE_VERIFICATION_SUCCEEDED:
        {
            bool can_apply = true;

            if (NULL != fw_update_server.model_data_cb)
            {
                fw_update_server_apply_t apply_data;
                apply_data.fw_image_idx = fw_update_server_ctx.update_fw_image_idx;
                apply_data.pcan_apply = &can_apply;
                fw_update_server.model_data_cb(&fw_update_server, FW_UPDATE_SERVER_APPLY, &apply_data);
            }

            if (!can_apply)
            {
                printe("fw_update_server_handle_update_apply: can't apply now");
                status = FW_UPDATE_STATUS_TEMP_UNAVAIL;
            }
            else
            {
                fw_update_server_ctx.update_phase = FW_UPDATE_PHASE_APPLYING_UPDATE;
            }

            printi("fw_update_server_handle_update_apply: applying update");
        }
        break;
    case FW_UPDATE_PHASE_APPLYING_UPDATE:
        printi("fw_update_server_handle_update_apply: already applying update");
        break;
    case FW_UPDATE_PHASE_IDLE:
    case FW_UPDATE_PHASE_TRANSFER_ERR:
    case FW_UPDATE_PHASE_VERIFICATION_FAILED:
    case FW_UPDATE_PHASE_TRANSFER_ACTIVE:
    case FW_UPDATE_PHASE_VERIFYING_UPDATE:
        printe("fw_update_server_handle_update_apply: apply update in wrong pahse(%d)",
               fw_update_server_ctx.update_phase);
        status = FW_UPDATE_STATUS_WRONG_PHASE;
        break;
    default:
        break;
    }

    fw_update_status(dst, app_key_index, status, fw_update_server_ctx.update_ttl,
                     fw_update_server_ctx.update_timeout_base, fw_update_server_ctx.blob_id,
                     fw_update_server_ctx.update_fw_image_idx);
}

bool fw_update_server_receive(mesh_msg_p pmesh_msg)
{
    bool ret = TRUE;
    uint8_t *pbuffer = pmesh_msg->pbuffer + pmesh_msg->msg_offset;
    switch (pmesh_msg->access_opcode)
    {
    case MESH_MSG_FW_UPDATE_INFO_GET:
        if (pmesh_msg->msg_len == sizeof(fw_update_info_get_t))
        {
            fw_update_info_get_t *pmsg = (fw_update_info_get_t *)pbuffer;
            fw_info_status(pmesh_msg->src, pmesh_msg->app_key_index, pmsg->first_index, pmsg->entries_limit);
        }
        break;
    case MESH_MSG_FW_UPDATE_FW_METADATA_CHECK:
        {
            fw_update_fw_metadata_check_t *pmsg = (fw_update_fw_metadata_check_t *)pbuffer;
            uint8_t metadata_len = pmesh_msg->msg_len - sizeof(fw_update_fw_metadata_check_t);
            uint8_t *pmetadata = pmsg->fw_metadata;
            if (metadata_len == 0)
            {
                pmetadata = NULL;
            }
            fw_metadata_status(pmesh_msg->src, pmesh_msg->app_key_index, pmsg->fw_image_idx, pmetadata,
                               metadata_len);
        }
        break;
    case MESH_MSG_FW_UPDATE_GET:
        if (pmesh_msg->msg_len == sizeof(fw_update_get_t))
        {
            fw_update_status(pmesh_msg->src, pmesh_msg->app_key_index, FW_UPDATE_STATUS_SUCCESS,
                             fw_update_server_ctx.update_ttl, fw_update_server_ctx.update_timeout_base,
                             fw_update_server_ctx.blob_id, fw_update_server_ctx.update_fw_image_idx);
        }
        break;
    case MESH_MSG_FW_UPDATE_START:
        {
            fw_update_server_handle_update_start(pmesh_msg);
        }
        break;
    case MESH_MSG_FW_UPDATE_CANCEL:
        if (pmesh_msg->msg_len == sizeof(fw_update_cancel_t))
        {
            printi("fw_update_server_receive: cancel firmware update in phase %d",
                   fw_update_server_ctx.update_phase);
            fw_update_server_clear();
            fw_update_status(pmesh_msg->src, pmesh_msg->app_key_index, FW_UPDATE_STATUS_SUCCESS,
                             fw_update_server_ctx.update_ttl, fw_update_server_ctx.update_timeout_base,
                             fw_update_server_ctx.blob_id, fw_update_server_ctx.update_fw_image_idx);
        }
        break;
    case MESH_MSG_FW_UPDATE_APPLY:
        if (pmesh_msg->msg_len == sizeof(fw_update_apply_t))
        {
            fw_update_server_handle_update_apply(pmesh_msg->src, pmesh_msg->app_key_index);
        }
        break;
    default:
        ret = false;
        break;
    }

    return ret;
}

bool fw_update_server_load(uint8_t *pfw_metadata, uint8_t metadata_len, uint8_t fw_image_index,
                           uint8_t update_ttl, uint8_t update_timeout_base, uint8_t blob_id[8])
{
    fw_info_e_t *pfw_info = fw_info_get(fw_image_index);
    if (NULL == pfw_info)
    {
        printe("fw_update_server_handle_update_start: invalid fw image index %d-%d", fw_image_index,
               fw_update_server_ctx.fw_info_list.count);
        return false;
    }

    fw_update_server_ctx.update_ttl = update_ttl;
    fw_update_server_ctx.update_timeout_base = update_timeout_base;
    fw_update_server_ctx.update_fw_image_idx = fw_image_index;
    memcpy(fw_update_server_ctx.blob_id, blob_id, 8);

    fw_update_server_ctx.update_phase = FW_UPDATE_PHASE_TRANSFER_ACTIVE;
    pfw_info->cached_metadata_len = metadata_len;
    memcpy(pfw_info->cached_metadata, pfw_metadata, metadata_len);
    /* prepare blob transfer */
    blob_transfer_server_init(blob_id, update_timeout_base, update_ttl);

    printi("fw_update_server_load: update_ttl %d, update_timeout_base %d, fw_image_idx %d",
           update_ttl, update_timeout_base, fw_image_index);
    printi("fw_update_server_load: blob id = ");
    dprinti(blob_id, 8);
    printi("fw_update_server_load: fw_metadata = ");
    dprinti(pfw_metadata, metadata_len);

    return true;
}

bool fw_update_server_reg(uint8_t element_index, model_data_cb_pf model_data_cb)
{
    fw_update_server.model_id = MESH_MODEL_FW_UPDATE_SERVER;
    fw_update_server.model_receive = fw_update_server_receive;
    fw_update_server.model_data_cb = model_data_cb;
    return mesh_model_reg(element_index, &fw_update_server);
}
#endif /* MESH_DFU */
